using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;


namespace KamLib.Linq.Expressions
{

    public class JsExpressionVisitor : ExpressionVisitor
    {

        public object Context { get; set; }

        public override string ToString() { return _sb.ToString(); }


        private static string GetOperator(ExpressionType nodeType)
        {
            switch (nodeType)
            {
                case ExpressionType.Assign:
                    return " = ";

                case ExpressionType.Add:
                    return " + ";
                case ExpressionType.Subtract:
                    return " - ";
                case ExpressionType.Multiply:
                    return " * ";
                case ExpressionType.Divide:
                    return " / ";
                case ExpressionType.Modulo:
                    return " % ";

                case ExpressionType.And:
                    return " & ";
                case ExpressionType.AndAlso:
                    return " && ";
                case ExpressionType.Or:
                    return " | ";
                case ExpressionType.OrElse:
                    return " || ";
                case ExpressionType.Not:
                    return "!";
                case ExpressionType.ExclusiveOr:
                    return " ^ ";


                case ExpressionType.Equal:
                    return " == ";
                case ExpressionType.NotEqual:
                    return " != ";
                case ExpressionType.LessThan:
                    return " < ";
                case ExpressionType.LessThanOrEqual:
                    return " <= ";
                case ExpressionType.GreaterThan:
                    return " > ";
                case ExpressionType.GreaterThanOrEqual:
                    return " >= ";

                // TODO: Add other operators...
            }
            throw new NotImplementedException("Operator not implemented");
        }



        #region Append

        protected readonly StringBuilder _sb = new StringBuilder();
        public void Clear() { _sb.Clear(); }

        public void Append(string arg0) { _sb.Append(arg0); }
        public void Append(char arg0) { _sb.Append(arg0); }
        public void Append(object arg0) { _sb.Append(arg0); }
        public void AppendLine(string arg0) { _sb.AppendLine(arg0); }

        public void AppendMethodParams(IEnumerable<Expression> prms)
        {
            Append('(');
            var isnext = false;
            foreach (var prm in prms)
            {
                if (isnext) Append(", "); else isnext = true;
                Visit(prm);
            }
            Append(')');
        }

        public void AppendSep()
        {
            if (InBlock() || InLambda())
                AppendLine(";");
        }

        #endregion


        #region Parent

        protected readonly List<Expression> Parents = new List<Expression>(16);
        protected Expression Parent { get { return Parents.Count < 2 ? null : Parents[Parents.Count - 2]; } }
        protected Expression Current { get { return Parents.Count < 1 ? null : Parents[Parents.Count - 1]; } }
        protected void Begin(Expression node) { Parents.Add(node); }
        protected void End() { Parents.RemoveAt(Parents.Count - 1); }

        public bool In(ExpressionType expressionType)
        {
            var p = Parent;
            return p != null && p.NodeType == expressionType;
        }
        public bool Is(ExpressionType expressionType)
        {
            var p = Current;
            return p != null && p.NodeType == expressionType;
        }

        public bool InAssign() { return In(ExpressionType.Assign); }
        public bool InBlock() { return In(ExpressionType.Block); }
        public bool InLambda() { return In(ExpressionType.Lambda); }

        public bool IsAssign() { return Is(ExpressionType.Assign); }
        public bool IsBlock() { return Is(ExpressionType.Block); }
        public bool IsLambda() { return Is(ExpressionType.Lambda); }

        #endregion


        #region Custom Visit

        [AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
        public class CustomVisitConstantAttribute : Attribute
        {
            public virtual bool Append(JsExpressionVisitor v, ConstantExpression node) { return false; }
        }


        [AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
        public class CustomVisitMemberAttribute : Attribute
        {
            public virtual bool AssignAsCall(JsExpressionVisitor v, MemberExpression node) { return false; }
            public virtual IEnumerable<Expression> GetCallParams(Expression[] prms) { return prms; }
            public virtual string GetMemberName(JsExpressionVisitor v, MemberExpression node, bool useCall) { return node.Member.Name; }

            public virtual bool Append(JsExpressionVisitor v, MemberExpression node, params Expression[] values)
            {
                v.Visit(node.Expression);
                v.Append(".");
                AppendMember(v, node, false, values);

                return true;
            }

            public virtual void AppendMember(JsExpressionVisitor v, MemberExpression node, bool useCall, params Expression[] values)
            {
                if (useCall || v.IsAssign() && AssignAsCall(v, node))
                {
                    v.Append(GetMemberName(v, node, true));
                    v.AppendMethodParams(GetCallParams(values));
                    v.AppendSep();
                }
                else
                    v.Append(GetMemberName(v, node, false));
            }

            public virtual bool AppendCall(JsExpressionVisitor v, MemberExpression node, params Expression[] args)
            {
                if (!AssignAsCall(v, node)) return false;

                v.Visit(node.Expression);
                v.Append(".");
                AppendMember(v, node, true, args);

                return true;
            }


        }


        [AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
        public class CustomVisitMethodAttribute : Attribute
        {
            public IEnumerable<Expression> GetCallParams(Expression[] prms) { return prms; }
            public virtual string GetMethodName(JsExpressionVisitor v, MethodCallExpression node) { return node.Method.Name; }

            public virtual bool Append(JsExpressionVisitor v, MethodCallExpression node)
            {
                v.Visit(node.Object);
                v.Append(".");
                AppendMethod(v, node);
                AppendParams(v, node);
                v.AppendSep();

                return true;
            }

            public virtual void AppendMethod(JsExpressionVisitor v, MethodCallExpression node)
            {
                v.Append(GetMethodName(v, node));
            }
            public virtual void AppendParams(JsExpressionVisitor v, MethodCallExpression node)
            {
                v.AppendMethodParams(GetCallParams(node.Arguments.ToArray()));
            }

        }


        #endregion


        #region Body

        protected override Expression VisitLambda<T>(Expression<T> node)
        {
            Begin(node);

            Append("function ");
            AppendMethodParams(node.Parameters);

            if (node.Body.NodeType == ExpressionType.Block)
                Visit(node.Body);
            else
            {
                AppendLine(" {");
                if (node.Body.Type != typeof(void))
                    Append("return ");
                Visit(node.Body);
                AppendLine(" }");
            }

            End();
            return node;
        }

        protected override Expression VisitBlock(BlockExpression node)
        {
            Begin(node);
            var inBlock = InBlock();

            if (!inBlock) AppendLine("{");

            if (node.Type == typeof(void))
                Visit(node.Expressions);
            else
            {
                for (int i = 0, len = node.Expressions.Count - 1; i < len; ++i)
                {
                    Visit(node.Expressions[i]);
                }
                Append("return ");
                Visit(node.Result);
                AppendLine(";");
            }

            if (!inBlock) AppendLine("}");

            End();
            return node;
        }

        protected override Expression VisitParameter(ParameterExpression node)
        {
            Append(node.Name);
            return node;
        }

        #endregion


        #region Expressions

        protected override Expression VisitConstant(ConstantExpression node)
        {
            if (node.Value == null)
                Append("null");
            else if (node.Type == typeof(JRaw))
                Append(node.Value);
            else if (node.Type == typeof(string))
            {
                Append('\'');
                Append(node.Value.ToString().Replace("'", "\'"));
                Append('\'');
            }
            else if (node.Type == typeof(bool))
            {
                Append((bool)node.Value ? "true" : "false");
            }
            else
            {
                var attr = node.Type.GetAttribute<CustomVisitConstantAttribute>();
                if (attr == null || !attr.Append(this, node))
                    Append(node.Value);
            }

            return node;
        }

        protected override Expression VisitBinary(BinaryExpression node)
        {
            Begin(node);

            if (node.NodeType == ExpressionType.Assign && node.Left.NodeType == ExpressionType.MemberAccess)
            {
                var mnode = (MemberExpression)node.Left;
                var attr = mnode.Member.GetAttribute<CustomVisitMemberAttribute>();
                if (attr != null && attr.AppendCall(this, mnode, node.Right))
                {
                    End();
                    return node;
                }
            }

            if (node.Left is BinaryExpression)
            {
                Append('(');
                Visit(node.Left);
                Append(')');
            }
            else
                Visit(node.Left);

            Append(GetOperator(node.NodeType));

            Visit(node.Right);

            if (node.NodeType == ExpressionType.Assign)
                AppendLine(";");

            End();
            return node;
        }

        #endregion


        #region Members


        protected override Expression VisitMember(MemberExpression node)
        {
            Begin(node);

            var attr = node.Member.GetAttribute<CustomVisitMemberAttribute>();
            if (attr == null || !attr.Append(this, node))
            {
                Visit(node.Expression);
                Append(".");
                Append(node.Member.Name);
            }

            End();
            return node;
        }

        protected override MemberAssignment VisitMemberAssignment(MemberAssignment node)
        {
            return base.VisitMemberAssignment(node);
        }

        protected override MemberBinding VisitMemberBinding(MemberBinding node)
        {
            return base.VisitMemberBinding(node);
        }

        protected override Expression VisitMemberInit(MemberInitExpression node)
        {
            return base.VisitMemberInit(node);
        }

        protected override MemberListBinding VisitMemberListBinding(MemberListBinding node)
        {
            return base.VisitMemberListBinding(node);
        }

        protected override MemberMemberBinding VisitMemberMemberBinding(MemberMemberBinding node)
        {
            return base.VisitMemberMemberBinding(node);
        }

        #endregion


        protected override Expression VisitMethodCall(MethodCallExpression node)
        {
            Begin(node);
            var attr = node.Method.GetAttribute<CustomVisitMethodAttribute>();

            if (attr == null || !attr.Append(this, node))
            {
                Visit(node.Object);
                Append('.');
                Append(node.Method.Name);
                AppendMethodParams(node.Arguments);
                AppendSep();
            }
            End();
            return node;
        }


        #region Conditionals

        protected override Expression VisitConditional(ConditionalExpression node)
        {
            Begin(node);

            Append("if (");
            Visit(node.Test);
            AppendLine(")");
            Visit(node.IfTrue);
            AppendLine("else");
            Visit(node.IfFalse);

            End();
            return node;
        }

        #endregion


        #region Utilites

        public static ConstantExpression Raw(string javaScript) { return Expression.Constant(new JRaw { Value = javaScript }); }

        public class JRaw
        {
            public string Value;
            public override string ToString() { return Value; }
        }


        public static object GetValue(Expression node)
        {
            if (node.NodeType == ExpressionType.Constant)
                return ((ConstantExpression)node).Value;

            if (node.NodeType == ExpressionType.MemberAccess)
            {
                var mnode = (MemberExpression)node;
                if (mnode.Expression.NodeType == ExpressionType.Constant)
                {
                    return mnode.Member.GetValue(((ConstantExpression)mnode.Expression).Value, null);
                }
            }

            throw new NotSupportedException();
        }

        #endregion


    }
}